﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Permissions;

using Moe.ECamel.Common.Contracts;
using Moe.ECamel.Common.Entities;
using Moe.ECamel.Common.Security;
using Moe.ECamel.Server.BL;

namespace Moe.ECamel.Server.Services
{
	public class RentalService : ECamelService, IRentalService
	{
		#region IRentalService Members

		[PrincipalPermission(SecurityAction.Demand, Role = Operations.RentalView)]
		public List<Rental> GetCustomerActiveRentals(int customerId)
		{
			using (var bl = new RentalBL(GetReadOnlyDataContext()))
			{
				return bl.GetCustomerActiveRentals(customerId);
			}
		}

		[PrincipalPermission(SecurityAction.Demand, Role = Operations.RentalView)]
		public List<Rental> GetCustomerRentals(int customerId)
		{
			using (var bl = new RentalBL(GetReadOnlyDataContext()))
			{
				return bl.GetCustomerRentals(customerId);
			}
		}

		[PrincipalPermission(SecurityAction.Demand, Role = Operations.RentalRent)]
		public RentalResponse RentItem(RentalRequest request)
		{
			var subscription = GetSubscription(request.CustomerId);
			// Validation: User has an active subscription:
			if (subscription == null)
				return new RentalResponse(new ECamelError(MessageIds.RentalInvalidNoActiveSubscription));

			var item = GetItem(request.ItemId);
			var subscriptionItemTypeInfo = GetSubscriptionItemTypeInfo(item.ItemTypeId, subscription);
			// Validation: The subscription includes items of the requested type:
			if (subscriptionItemTypeInfo.Quantity == 0)
				return new RentalResponse(new ECamelError(MessageIds.RentalInvalidExceedsSubscription));

			using (var bl = new RentalBL(GetReadOnlyDataContext()))
			{
				// Validation: Rental request does not exceed subscription details:
				int currentQuantity = bl.GetActiveRentalsByCustomerAndItemType(request.CustomerId, subscriptionItemTypeInfo.ItemTypeId);
				if (currentQuantity + 1 > subscriptionItemTypeInfo.Quantity)
					return new RentalResponse(new ECamelError(MessageIds.RentalInvalidExceedsSubscription));
			}

			Inventory inventory;
			// Check if there is a reserved inventory of this item for this customer:
			using (var itemReservationService = new ItemReservationService(this))
			{
				inventory = itemReservationService.GetReservedInventory(request.ItemId, request.CustomerId);
			}

			// If not, Get an available inventory id for the item in the library:
			if (inventory == null)
			{
				var customer = GetCustomer(request.CustomerId);
				using (var inventoryService = new InventoryService(this))
				{
					inventory = inventoryService.GetAvailableInventory(customer.LibraryId, request.ItemId);
				}				
			}

			// If no inventory was found, abort rental:
			if (inventory == null)
				return new RentalResponse(new ECamelError(MessageIds.RentalInvalidNoAvailableInventory));
			// Mark allocated inventory as not available:
			inventory.IsAvailable = false;

			var subscriptionTypeItemTypeInfo = GetSubscriptionTypeItemTypeInfo(subscriptionItemTypeInfo);

			// Create a rental record and save it:
			var rental = new Rental
			             	{
								InventoryId = inventory.InventoryId,
								CustomerId = request.CustomerId,
								RentalDate =  request.RentalDate,
								ExpectedReturnDate = request.RentalDate.AddDays(subscriptionTypeItemTypeInfo.MaxRentalDays),
								Active = true
			             	};
			using (var bl = new RentalBL(GetReadWriteDataContext()))
			{
				bl.RentItem(rental);
			}

			SubmitChanges();
			return new RentalResponse(rental);
		}

		[PrincipalPermission(SecurityAction.Demand, Role = Operations.RentalReturn)]
		public ReturnRentalResponse ReturnItem(int rentalId, DateTime dateOfReturn)
		{
			Rental rental;
			// Get rental by ID:
			using (var bl = new RentalBL(GetReadWriteDataContext()))
			{
				rental = bl.GetRentalById(rentalId);
			}

			var subscription = GetSubscription(rental.CustomerId);
			var subscriptionItemTypeInfo = GetSubscriptionItemTypeInfo(rental.ItemTypeId, subscription);
			var subscriptionTypeItemTypeInfo = GetSubscriptionTypeItemTypeInfo(subscriptionItemTypeInfo);
			// Set return date:
			rental.ActualReturnDate = dateOfReturn;

			// Validate:
			ECamelError error = ValidateRentalReturn(rental);
			if (error != null)
				return new ReturnRentalResponse { Error = error };
			var response = new ReturnRentalResponse();
			// Calculate fine, if any:
			response.ExtraDays = (rental.ActualReturnDate.Value - rental.ExpectedReturnDate).Days;
			if (response.ExtraDays < 0)
				response.ExtraDays = 0;
			response.FinePerExtraDay = subscriptionTypeItemTypeInfo.FinePerExtraRentalDay;
			rental.Fine = response.Fine = response.FinePerExtraDay * response.ExtraDays;
			// Rental remains active if there's a fine to pay:
			rental.Active = rental.Fine != 0;
			// Update customer's debt:
			rental.Customer.TotalDebt += rental.Fine;

			using (var itemReservationService = new ItemReservationService(this))
			{
				// Check waiting list to see if someone is waiting for a copy of this item:
				itemReservationService.ReserveInventoryIfNeeded(rental.Inventory, dateOfReturn);
			}

			SubmitChanges();
			return response;
		}

		[PrincipalPermission(SecurityAction.Demand, Role = Operations.RentalReturn)]
		public ECamelError PayFine(int rentalId, DateTime paymentDate)
		{
			Rental rental;
			using (var bl = new RentalBL(GetReadWriteDataContext()))
			{
				rental = bl.GetRentalById(rentalId);
			}

			var error = rental.ValidateFinePayment();
			if (error != null)
				return error;

			rental.FinePayment = new Payment();
			rental.FinePayment.CustomerId = rental.CustomerId;
			rental.FinePayment.Amount = rental.Fine;
			rental.FinePayment.PayedOn = paymentDate;
			rental.FinePayment.Description = "תשלום עבור קנס על החזרה מאוחרת.";

			using (var paymentService = new PaymentService(this))
			{
				paymentService.AddPayments(new Payment[] { rental.FinePayment });
			}

			// Decativate rental:
			rental.Active = false;

			// Update customer debt:
			rental.Customer.TotalDebt -= rental.Fine;

			SubmitChanges();
			return null;
		}

		#endregion

		private Subscription GetSubscription(int customerId)
		{
			using (var subscriptionService = new SubscriptionService(this))
			{
				return subscriptionService.GetActiveSubscriptionOfCustomer(customerId);
			}
		}
		private Item GetItem(int itemId)
		{
			using (var itemService = new ItemService(this))
			{
				return itemService.GetItemById(itemId);
			}
		}

		private SubscriptionItemTypeInfo GetSubscriptionItemTypeInfo(int itemTypeId, Subscription subscription)
		{
			return (from siti in subscription.SubscriptionItemTypeInfos
					where siti.ItemTypeId == itemTypeId
					select siti).Single();
		}
		private SubscriptionTypeItemTypeInfo GetSubscriptionTypeItemTypeInfo(SubscriptionItemTypeInfo subscriptionItemTypeInfo)
		{
			var dataContext = GetReadWriteDataContext();
			return (from stiti in dataContext.SubscriptionTypeItemTypeInfos
			          where stiti.SubscriptionTypeId == subscriptionItemTypeInfo.Subscription.SubscriptionTypeId &&
			                stiti.ItemTypeId == subscriptionItemTypeInfo.ItemTypeId
			          select stiti).Single();
		}

		private Customer GetCustomer(int customerId)
		{
			using (var customerService = new CustomerService(this))
			{
				return customerService.GetCustomerById(customerId);
			}
		}

		private static ECamelError ValidateRentalReturn(Rental rental)
		{
			if (rental.ActualReturnDate == null)
				return  new ECamelError(MessageIds.RentalReturnInvalidMissingFields, "תאריך החזרה");

			if (rental.ActualReturnDate < rental.RentalDate)
				return new ECamelError(MessageIds.RentalReturnInvalidReturnBeforeRental);

			return null;
		}

		public void ProcessUnreturnedRentals(DateTime date)
		{
			IList<Rental> overdueRentals;
			using (var bl = new RentalBL(GetReadWriteDataContext()))
			{
				overdueRentals = bl.GetOverdueRentals(date);
			}

			using (var mailMessageService = new MailMessageService(this))
			{
				foreach (var rental in overdueRentals)
				{
					var subject = "התראה על איחור בהחזרת פריט שכור";
					var body = 
						"בתאריך " + rental.RentalDate.ToShortDateString() + " שכרת את הפריט '" +  rental.ItemTitle + "' מהספריה." + Environment.NewLine +
                        "על-פי תנאי המנוי עליך להחזיר את הפריט לספריה עד לתאריך " + rental.ExpectedReturnDate.ToShortDateString() + ", אך על-פי רישומינו טרם עשית זאת." + Environment.NewLine + 
                        "נבקשך להחזיר את הפריט בהקדם, על-מנת שלקוחות אחרים יוכלו לשכור אותו. להזכירך, כל יום איחור נוסף יגדיל את הקנס על האיחור.";
					var mailMessage = new MailMessage(rental.Customer, date, subject, body);

					mailMessageService.SendMessage(mailMessage);
					rental.OverdueMailMessage = mailMessage;
				}
			}

			SubmitChanges();
		}
	}
}